﻿using System;
using System.Collections.Generic;
using System.Linq;
using MoroMoro.Samples.ToDoList.Base.Domain.Specifications;
using MoroMoro.Samples.ToDoList.Base.Domain.Viewpoints;

namespace MoroMoro.Samples.ToDoList.Base.Persistence
{
    /// <summary>
    /// リポジトリを実装するための基本クラスです。
    /// </summary>
    /// <typeparam name="TEntity">エンティティの型。</typeparam>
    internal abstract class RepositoryBase<TEntity> : IRepository<TEntity>
        where TEntity : class
    {
        /// <inheritdoc/>
        public TEntity Find(Specification<TEntity> specification)
        {
            if (specification == null)
            {
                throw new ArgumentNullException("specification");
            }
            var query = CreateQuery(specification);
            return query.SingleOrDefault();
        }

        /// <inheritdoc/>
        public List<TEntity> FindList(Specification<TEntity> specification, Order<TEntity> order = null, int? skipCount = null, int? takeCount = null)
        {
            var query = CreateQuery(specification, order, skipCount, takeCount);
            return query.ToList();
        }

        /// <inheritdoc/>
        public List<TEntity> FindAll(Order<TEntity> order = null, int? skipCount = null, int? takeCount = null)
        {
            var query = CreateQuery(null, order, skipCount, takeCount);
            return query.ToList();
        }


        /// <inheritdoc/>
        public bool Exists(Specification<TEntity> specification)
        {
            var query = CreateQuery(specification);
            return query.Any();
        }


        /// <inheritdoc/>
        public int GetCount(Specification<TEntity> specification)
        {
            var query = CreateQuery(specification);
            return query.Count();
        }


        /// <summary>
        /// クエリを生成します。
        /// </summary>
        /// <param name="specification">満たすべき仕様。</param>
        /// <param name="order">並べ替えの基準となる順序。</param>
        /// <param name="skipCount">取得対象から除外する要素数。</param>
        /// <param name="takeCount">取得する要素数。</param>
        /// <returns>クエリ。</returns>
        private IQueryable<TEntity> CreateQuery(Specification<TEntity> specification, Order<TEntity> order = null, int? skipCount = null, int? takeCount = null)
        {
            var query = CreateBaseQuery();
            if (specification != null)
            {
                query = query.Where(specification.Expression);
            }
            if (order != null)
            {
                query = order.ApplyTo(query);
            }
            if (skipCount.HasValue)
            {
                query = query.Skip(skipCount.Value);
            }
            if (takeCount.HasValue)
            {
                query = query.Take(takeCount.Value);
            }
            return query;
        }

        /// <summary>
        /// 基になるクエリを生成します。
        /// </summary>
        /// <returns>基になるクエリ。</returns>
        protected abstract IQueryable<TEntity> CreateBaseQuery();


        /// <inheritdoc/>
        public abstract void Add(TEntity target);

        /// <inheritdoc/>
        public abstract void Remove(TEntity target);
    }
}
